library LastOrder initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//*
//* LastOrder is a library that was designed to allow interfacing with the last
//* N orders any unit on your map has received. This library was also designed
//* to be used as a means to reissue lost orders to a unit either after
//* preventing a spell cast or in many other situations.
//*
//* There are two configuration constants for you to play with in using this
//* script. ORDERS_TO_HOLD is basically the size of the game's memory for each
//* individual unit. The larger the number is, the further back you can retrace
//* a unit's order list. Setting this value to 3 suffices for all actual
//* mechanics purposes. Raise it only if you have a specific application where
//* you need more. Lowering it to 2 covers most cases, but may miss a few, and
//* lowering it to 1 prevents you from adequately canceling spells and properly
//* reissuing previous orders. I recommend leaving it alone at 3. The MAX_ORDERS
//* constant is the number of orders that the system holds over all units at a
//* given time. If you are worried about running out, go ahead and increase it,
//* but be aware that it will use big arrays (which are slower) if you use a
//* number over 8191. Don't lower it under 8191, there's no reason to. Don't
//* change the non-private constants, there's no reason to.
//*
//*     function GetPastOrder takes unit u, integer whichOrder returns Order
//*     function GetPastOrderId takes unit u, integer whichOrder returns integer
//*     function GetPastOrderString takes unit u, integer whichOrder returns string
//*     function GetPastOrderType takes unit u, integer whichOrder returns integer
//*     function GetPastOrderX takes unit u, integer whichOrder returns real
//*     function GetPastOrderY takes unit u, integer whichOrder returns real
//*     function GetPastOrderTarget takes unit u, integer whichOrder returns widget
//*
//* The above API is the main list of functions the user can use to interface
//* with past orders. If you want the immediate last order for a player, use 1
//* for the whichOrder integer. The 'Order' returned by GetPastOrder is a struct
//* that contains all information of an issued order. If you want to interface
//* directly with the struct and skip all of the interfacing wrappers, you have
//* access to them via the following commands:
//*
//*     [unit]     .u       The unit being ordered.
//*     [integer]  .id      The order id of the past order.
//*     [integer]  .typ     The type of the past order. (See constants)
//*     [boolean]  .fin     A flag indicating whether the order was finished.
//*     [widget]   .tar     The target widget of the past order.
//*     [real]     .x       The x coordinate of the target point of the order.
//*     [real]     .y       The y coordinate of the target point of the order.
//*
//* There is also a sizable API for backwards compatibility with older versions
//* of the library. This API assumes that you're talking about a specific past
//* order, either the lastmost order or the second lastmost. In most cases,
//* these are still useful because they remove an argument from the call.
//*
//*     function GetLastOrder takes unit u returns Order
//*     function GetLastOrderId takes unit u returns integer
//*     function GetLastOrderString takes unit u returns string
//*     function GetLastOrderType takes unit u returns integer
//*     function GetLastOrderX takes unit u returns real
//*     function GetLastOrderY takes unit u returns real
//*     function GetLastOrderTarget takes unit u returns widget
//*     function IsLastOrderFinished takes unit u returns boolean
//*
//* Besides being able to get information about all of the past orders a unit
//* has been issued, the most useful part of this system is actually reissuing
//* those orders as a means to fix lost orders or intercept and prevent spell
//* casting. The following API is then available to the user:
//*
//*     function IssuePastOrder takes unit u, integer whichOrder returns boolean
//*     function IssueLastOrder takes unit u returns boolean
//*     function IssueSecondLastOrder takes unit u returns boolean
//*     function AbortOrder takes unit u returns boolean
//*
//* If you want to reissue a past order for a given unit, IssuePastOrder is the
//* function that you'll want to use on a unit. To issue the last or second last
//* orders, there are functions for those as well. (They mostly exist for
//* backwards compatibility) AbortOrder is a means for anyone using this script
//* to stop a unit from running any given order that they happen to have at any
//* moment. AbortOrder is used in conjunction with a supplementary library,
//* AbortSpell, to seamlessly replicate WC3's spell error messages.
//*
//*     function IssueArbitraryOrder takes unit u, Order o returns boolean
//*
//* IssueArbitraryOrder is special in that it ignores many of the normal checks
//* and automatically forces a unit to take on a specific order. This can be
//* convenient if you want to make one unit take on another unit's order, for
//* instance. Be forewarned that this overwrites whatever the unit was doing and
//* issues the order no matter what, so use only as needed.
//*
//* If you have any further questions regarding LastOrder or how to use it, feel
//* free to visit [url]www.wc3c.net[/url] and ask questions there. This library should only
//* ever be released at WC3C and at no other site. Please give credits if this
//* library finds its way into your maps, and otherwise thanks for reading!
//*
//* Enjoy!
//*
globals
    //Order type variables
            constant integer ORDER_TYPE_TARGET    = 1
            constant integer ORDER_TYPE_POINT     = 2
            constant integer ORDER_TYPE_IMMEDIATE = 3

    //System constants
    private constant integer ORDERS_TO_HOLD       = 3    //How many past orders the
                                                         //system holds for each unit.
                                                         //Should be at least 2.
    private constant integer MAX_ORDERS           = 8191 //The max number of orders
                                                         //the system can maintain.
                                                         //Going over 8191 uses big
                                                         //arrays, which are slower
                                                         //than normal arrays.
endglobals

globals
    private hashtable ht = InitHashtable()
endglobals

struct Order[MAX_ORDERS]
    unit    u
    integer id
    integer typ
    boolean fin
    widget  tar
    real    x
    real    y
    static method create takes unit ordered, integer ordid, integer ordtyp, widget target, real ordx, real ordy returns Order
        local Order   o   = Order.allocate()
        local integer i   = ORDERS_TO_HOLD
        local integer hid = GetHandleId(ordered)

        set o.u   = ordered
        set o.id  = ordid
        set o.typ = ordtyp
        set o.fin = false
        set o.tar = target
        set o.x   = ordx
        set o.y   = ordy
        //Handle stored orders in the hashtable
        loop
            //We hold up to the constant ORDERS_TO_HOLD in the table
            exitwhen i == 1
            //Moves the N-1th order to the Nth slot, etc. except storing new last order
            if HaveSavedInteger(ht, hid, i-1) then
                if i == ORDERS_TO_HOLD and HaveSavedInteger(ht, hid, i) then
                    //Destroy lastmost order struct
                    call Order.destroy(Order(LoadInteger(ht, hid, i)))
                endif
                //Can only do this if the N-1th order exists
                call SaveInteger(ht, hid, i, LoadInteger(ht, hid, i-1))
            endif
            set i = i - 1
        endloop
        //Store the new order to the hashtable as 1th last order
        call SaveInteger(ht, hid, 1, integer(o))
        return o
    endmethod
endstruct

//******************************************************************************

//! textmacro LastOrderDebug takes ORDER, RETURN
    debug if $ORDER$ > ORDERS_TO_HOLD then
    debug     call BJDebugMsg(SCOPE_PREFIX+"Error: Order out of range")
    debug     return $RETURN$
    debug endif
    debug if not HaveSavedInteger(ht, GetHandleId(u), $ORDER$) then
    debug     call BJDebugMsg(SCOPE_PREFIX+"Error: The "+I2S($ORDER$)+"th order doesn't exist")
    debug     return $RETURN$
    debug endif
//! endtextmacro

function GetPastOrder takes unit u, integer whichOrder returns Order
    //! runtextmacro LastOrderDebug("whichOrder", "0")
    return Order(LoadInteger(ht, GetHandleId(u), whichOrder))
endfunction
function GetPastOrderId takes unit u, integer whichOrder returns integer
    //! runtextmacro LastOrderDebug("whichOrder", "0")
    return Order(LoadInteger(ht, GetHandleId(u), whichOrder)).id
endfunction
function GetPastOrderString takes unit u, integer whichOrder returns string
    //! runtextmacro LastOrderDebug("whichOrder", "\"\"")
    return OrderId2String(Order(LoadInteger(ht, GetHandleId(u), whichOrder)).id)
endfunction
function GetPastOrderType takes unit u, integer whichOrder returns integer
    //! runtextmacro LastOrderDebug("whichOrder", "0")
    return Order(LoadInteger(ht, GetHandleId(u), whichOrder)).typ
endfunction
function GetPastOrderX takes unit u, integer whichOrder returns real
    //! runtextmacro LastOrderDebug("whichOrder", "0.")
    return Order(LoadInteger(ht, GetHandleId(u), whichOrder)).x
endfunction
function GetPastOrderY takes unit u, integer whichOrder returns real
    //! runtextmacro LastOrderDebug("whichOrder", "0.")
    return Order(LoadInteger(ht, GetHandleId(u), whichOrder)).y
endfunction
function GetPastOrderTarget takes unit u, integer whichOrder returns widget
    //! runtextmacro LastOrderDebug("whichOrder", "null")
    return Order(LoadInteger(ht, GetHandleId(u), whichOrder)).tar
endfunction

//******************************************************************************

function GetLastOrder takes unit u returns Order
    return GetPastOrder(u, 1)
endfunction
function GetLastOrderId takes unit u returns integer
    return GetPastOrderId(u, 1)
endfunction
function GetLastOrderString takes unit u returns string
    return GetPastOrderString(u, 1)
endfunction
function GetLastOrderType takes unit u returns integer
    return GetPastOrderType(u, 1)
endfunction
function GetLastOrderX takes unit u returns real
    return GetPastOrderX(u, 1)
endfunction
function GetLastOrderY takes unit u returns real
    return GetPastOrderY(u, 1)
endfunction
function GetLastOrderTarget takes unit u returns widget
    return GetPastOrderTarget(u, 1)
endfunction
function IsLastOrderFinished takes unit u returns boolean
    //! runtextmacro LastOrderDebug("1", "false")
    return GetUnitCurrentOrder(u) == 0 or Order(LoadInteger(ht, GetHandleId(u), 1)).fin
endfunction

//******************************************************************************

private function OrderFilter takes unit u, integer id returns boolean
    //* Excludes specific orders or unit types from registering with the system
    //*
    //* 851972: stop
    //*         Stop is excluded from the system because it is the order that
    //*         tells a unit to do nothing. It should be ignored by the system.
    //*
    //* 851971: smart
    //* 851986: move
    //* 851983: attack
    //* 851984: attackground
    //* 851990: patrol
    //* 851993: holdposition
    //*         These are the UI orders that are passed to the system.
    //*
    //* 851973: stunned
    //*         This order is issued when a unit is stunned onto the stunner
    //*         It's ignored by the system, since you'd never want to reissue it
    //*
    //* >= 852055, <= 852762
    //*         These are all spell IDs from defend to incineratearrowoff with
    //*         a bit of leeway at the ends for orders with no strings.
    //*
    return id == 851971 or id == 851986 or id == 851983 or id == 851984 or id == 851990 or id == 851993 or (id >= 852055 and id <= 852762)
endfunction

private function IssuePastOrderFilter takes unit u, integer whichOrder returns boolean
    //* Some criteria for whether or not a unit's last order should be given
    //*
    //* INSTANT type orders are excluded because generally, reissuing an instant
    //* order doesn't make sense. You can remove that check below if you'd like,
    //* though.
    //*
    //* The Type check is really just to ensure that no spell recursion can
    //* occur with IssueLastOrder. The problem with intercepting the spell cast
    //* event is that it happens after the order is 'caught' and registered to
    //* this system. Therefore, to just IssueLastOrder tells it to recast the
    //* spell! That's a problem, so we need a method to eliminate it.
    //*
    return GetUnitTypeId(u) != 0 and not IsUnitType(u, UNIT_TYPE_DEAD) and GetPastOrderType(u, whichOrder) != 0 and GetPastOrderType(u, whichOrder) != ORDER_TYPE_IMMEDIATE
endfunction

//******************************************************************************

function IssuePastOrder takes unit u, integer whichOrder returns boolean
    local Order o = GetPastOrder(u, whichOrder)
    if IssuePastOrderFilter(u, whichOrder) and not o.fin then
        if o.typ == ORDER_TYPE_TARGET then
            return IssueTargetOrderById(u, o.id, o.tar)
        elseif o.typ == ORDER_TYPE_POINT then
            if o.id == 851971 then
                //This adjusts for a bug in the point order's boolean return
                //when issuing a smart order
                call IssuePointOrderById(u, o.id, o.x, o.y)
                return true
            else
                return IssuePointOrderById(u, o.id, o.x, o.y)
            endif
        elseif o.typ == ORDER_TYPE_IMMEDIATE then
            return IssueImmediateOrderById(u, o.id)
        endif
    endif
    return false
endfunction

function IssueLastOrder takes unit u returns boolean
    return IssuePastOrder(u, 1)
endfunction

function IssueSecondLastOrder takes unit u returns boolean
    return IssuePastOrder(u, 2)
endfunction

function IssueArbitraryOrder takes unit u, Order o returns boolean
    if o.typ == ORDER_TYPE_TARGET then
        return IssueTargetOrderById(u, o.id, o.tar)
    elseif o.typ == ORDER_TYPE_POINT then
        if o.id == 851971 then
            //This adjusts for a bug in the point order's boolean return
            //when issuing a smart order
            call IssuePointOrderById(u, o.id, o.x, o.y)
            return true
        else
            return IssuePointOrderById(u, o.id, o.x, o.y)
        endif
    elseif o.typ == ORDER_TYPE_IMMEDIATE then
        return IssueImmediateOrderById(u, o.id)
    endif
    return false
endfunction

function AbortOrder takes unit u returns boolean
    if IsUnitPaused(u) then
        return false
    else
        call PauseUnit(u, true)
        call IssueImmediateOrder(u, "stop")
        call PauseUnit(u, false)
    endif
    return true
endfunction

//**********************************************************

private function Conditions takes nothing returns boolean
    return OrderFilter(GetTriggerUnit(), GetIssuedOrderId())
endfunction

private function Actions takes nothing returns nothing
    local unit    u   = GetTriggerUnit()
    local widget  t   = GetOrderTarget()
    local integer oid = GetIssuedOrderId()
    local integer oty = 0

    if GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER then
        call Order.create(u, oid, ORDER_TYPE_TARGET, t, GetWidgetX(t), GetWidgetY(t))
    elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER then
        call Order.create(u, oid, ORDER_TYPE_POINT, null, GetOrderPointX(), GetOrderPointY())
    elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_ORDER then
        call Order.create(u, oid, ORDER_TYPE_IMMEDIATE, null, GetUnitX(u), GetUnitY(u))
    debug else
        debug call BJDebugMsg(SCOPE_PREFIX+"Error: Invalid order type")
    endif

    set u = null
    set t = null
endfunction

//**********************************************************

private function SpellActions takes nothing returns nothing
    local Order o = GetPastOrder(GetTriggerUnit(), 1)
    set o.fin = true
endfunction

//**********************************************************

private function OnAdd takes nothing returns boolean
    local integer hid = GetHandleId(GetFilterUnit())
    local integer i   = ORDERS_TO_HOLD

    //Handle stored orders in the hashtable
    loop
        //We hold up to the constant ORDERS_TO_HOLD in the table
        exitwhen i == 0
        //If any of the N orders exist for this handle id, kill them all
        if HaveSavedInteger(ht, hid, i) then
            call Order.destroy(Order(LoadInteger(ht, hid, i)))
            call RemoveSavedInteger(ht, hid, i)
        endif
        set i = i - 1
    endloop

    return false
endfunction

//**********************************************************

private function Init takes nothing returns nothing
    local trigger trg = CreateTrigger()
    local region  re  = CreateRegion()
    local rect    m   = GetWorldBounds()
    
    //Main order catching trigger
    call TriggerAddAction(trg, function Actions)
    call TriggerAddCondition(trg, Condition(function Conditions))
    call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
    call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_ORDER)

    //Spell trigger to set a flag that indicates a spell order's completion
    set trg = CreateTrigger()
    call TriggerAddAction(trg, function SpellActions)
    call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_SPELL_EFFECT)

    //Entering world trigger that clears old data from handle ids
    set trg = CreateTrigger()
    call RegionAddRect(re, m)
    call TriggerRegisterEnterRegion(trg, re, Condition(function OnAdd))
    call RemoveRect(m)

    set trg = null
    set re  = null
    set m   = null
endfunction
endlibrary
